#include <string.h>
#include <android/log.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <assert.h>
#include "com_elinkway_infinitemovies_utils_TokenUtil.h"

#define JNIREG_CLASS "com/elinkway/infinitemovies/utils/TokenUtil" //the class name to be register

#define MOUNTAIN "35390"
#define RIVER "17606"
#define CLOUD "30827"

#define AES "FqGmz"
#define DES "MxQXOz"

//#define LOG(args...) __android_log_print(ANDROID_LOG_DEBUG, "checkSignature" , ## args)

static JNINativeMethod gMethods[] = {
        {"getToken", "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;", (void*)getToken},
        {"getCode", "(Landroid/content/Context;)Ljava/lang/String;", (void*)getCode},
};

static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static int registerNatives(JNIEnv* env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
        return JNI_FALSE;
    return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {
        return -1;
    }
    result = JNI_VERSION_1_4;
    return result;
}

jstring toString(JNIEnv *env, jbyteArray byteArray) {
    jclass string_cls = env->FindClass("java/lang/String");
    jmethodID new_string_mid = env->GetMethodID(string_cls, "<init>", "([BLjava/lang/String;)V");
    return reinterpret_cast<jstring>(env->NewObject(string_cls, new_string_mid, byteArray,
             env->NewStringUTF(UTF_8)));
}

jbyteArray toBytes(JNIEnv *env, const char *bytes) {
    jclass string_cls = env->FindClass("java/lang/String");
    jmethodID get_bytes_mid = env->GetMethodID(string_cls, "getBytes", "(Ljava/lang/String;)[B");
    return reinterpret_cast<jbyteArray>(env->CallObjectMethod(
            env->NewStringUTF(bytes), get_bytes_mid, env->NewStringUTF(UTF_8)));
}

jbyteArray toBytes(JNIEnv *env, jstring string) {
    jclass string_cls = env->FindClass("java/lang/String");
    jmethodID get_bytes_mid = env->GetMethodID(string_cls, "getBytes", "(Ljava/lang/String;)[B");
    return reinterpret_cast<jbyteArray>(env->CallObjectMethod(string, get_bytes_mid,
             env->NewStringUTF(UTF_8)));
}

jbyteArray getDigestedBytes(JNIEnv *env, jbyteArray complex_bytes) {
    static jobject satic_message_digest_obj = __null;
    jclass message_digest_cls = env->FindClass("java/security/MessageDigest");
    jmethodID get_instance_mid = env->GetStaticMethodID(message_digest_cls, "getInstance",
                                   "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    if (satic_message_digest_obj == __null) {
        jobject local_message_digest_obj = env->CallStaticObjectMethod(
                message_digest_cls, get_instance_mid, env->NewStringUTF("MD5"));
        satic_message_digest_obj = env->NewGlobalRef(local_message_digest_obj);
        env->DeleteLocalRef(local_message_digest_obj);
    }
    jmethodID digest_mid = env->GetMethodID(message_digest_cls, "digest", "([B)[B");
    env->DeleteLocalRef(message_digest_cls);
    return reinterpret_cast<jbyteArray>(env->CallObjectMethod(
            satic_message_digest_obj, digest_mid, complex_bytes));
}

jstring toHex(JNIEnv *env, jbyteArray arr) {
    jstring ret = NULL;
    jsize len = env->GetArrayLength(arr);
    if (len > 0) {
        char chs[len * 2 + 1];
        jboolean b = JNI_FALSE;
        jbyte *data = env->GetByteArrayElements(arr, &b);
        int index;
        for (index = 0; index < len; index++) {
            jbyte bc = data[index];
            //Get high and low position
            jbyte h = (jbyte) ((bc >> 4) & 0x0f);
            jbyte l = (jbyte) (bc & 0x0f);
            jchar ch;
            jchar cl;

            if (h > 9) {
                ch = (jchar) ('a' + (h - 10));
            } else {
                ch = (jchar) ('0' + h);
            }

            if (l > 9) {
                cl = (jchar) ('a' + (l - 10));
            } else {
                cl = (jchar) ('0' + l);
            }
            chs[index * 2] = (char) ch;
            chs[index * 2 + 1] = (char) cl;
        }
        chs[len * 2] = 0;
        env->ReleaseByteArrayElements(arr, data, JNI_ABORT);
        ret = env->NewStringUTF(chs);
    }
    return ret;
}

//MD5
jstring getSecStr(JNIEnv *env, jstring jInfo) {
    jbyteArray digested_bytes = getDigestedBytes(env, toBytes(env, jInfo));
    return toHex(env, digested_bytes);
}

//just append string
jstring getAppendStr(JNIEnv *env, jclass thiz, jstring s1, jstring s2) {
    const char *s1x = (env)->GetStringUTFChars(s1, NULL);
    const char *s2x = (env)->GetStringUTFChars(s2, NULL);

    char *sall = new char[strlen(s1x) + strlen(s2x) + 1];
    strcpy(sall, s1x);
    strcat(sall, s2x);

    jstring retval = (env)->NewStringUTF(sall);

    (env)->ReleaseStringUTFChars(s1, s1x);
    (env)->ReleaseStringUTFChars(s2, s2x);

    return retval;
}

jobject getInstance(JNIEnv *env, jclass obj_class) {
    jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
    jobject obj = env->NewObject(obj_class, construction_id);
    return obj;
}

jstring getDeviceID(JNIEnv *env, jobject thiz, jobject mContext) {
    jclass cls_context = (env)->FindClass("android/content/Context");
    if (cls_context == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jmethodID getSystemService = (env)->GetMethodID(cls_context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
    if (getSystemService == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jfieldID TELEPHONY_SERVICE = (env)->GetStaticFieldID(cls_context, "TELEPHONY_SERVICE", "Ljava/lang/String;");
    if (TELEPHONY_SERVICE == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jobject str = (env)->GetStaticObjectField(cls_context, TELEPHONY_SERVICE);
    jobject telephonymanager = (env)->CallObjectMethod(mContext, getSystemService, str);
    if (telephonymanager == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jclass cls_tm = (env)->FindClass("android/telephony/TelephonyManager");
    if (cls_tm == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jmethodID getDeviceId = (env)->GetMethodID(cls_tm, "getDeviceId", "()Ljava/lang/String;");
    if (getDeviceId == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jobject deviceid = (env)->CallObjectMethod(telephonymanager, getDeviceId);
    return (jstring) deviceid;
}

jstring getSerialNumber(JNIEnv *env, jobject thiz, jobject mContext) {
    jclass cls_tm = (env)->FindClass("android/os/SystemProperties");
    if (cls_tm == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jmethodID getDeviceId = (env)->GetStaticMethodID(cls_tm, "get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if (getDeviceId == 0) {
        return (env)->NewStringUTF("unknown");
    }
    jstring param1 = (env)->NewStringUTF("ro.serialno");
    jstring param2 = (env)->NewStringUTF("unknown");
    jobject deviceid = (env)->CallStaticObjectMethod(cls_tm, getDeviceId, param1, param2);
    return (jstring) deviceid;
}

jstring jint2jstring(JNIEnv *env, jint first) {
    char buf[64]; // assumed large enough to cope with result
    sprintf(buf, "%d", first);  // error checking omitted
    return env->NewStringUTF(buf);
}

jstring getPublicKey(JNIEnv *env, jobject thiz, jobject context) {
    jclass context_cls = env->GetObjectClass(context);
    jmethodID get_package_manager_mid = env->GetMethodID(context_cls, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jmethodID get_package_name_mid = env->GetMethodID(context_cls, "getPackageName", "()Ljava/lang/String;");
    env->DeleteLocalRef(context_cls);
    jobject pm_obj = env->CallObjectMethod(context, get_package_manager_mid);
    jclass pm_cls = env->FindClass("android/content/pm/PackageManager");
    jmethodID get_package_info_mid = env->GetMethodID(pm_cls, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jstring package_name = reinterpret_cast<jstring>(env->CallObjectMethod(
            context, get_package_name_mid));
    jfieldID flag_fid = env->GetStaticFieldID(pm_cls, "GET_SIGNATURES", "I");
    jint flag = env->GetStaticIntField(pm_cls, flag_fid);
    env->DeleteLocalRef(pm_cls);
    jobject pi_obj = env->CallObjectMethod(pm_obj, get_package_info_mid,
                                           package_name, flag);
    env->DeleteLocalRef(package_name);
    jclass pi_cls = env->FindClass("android/content/pm/PackageInfo");
    jfieldID signatures_fid = env->GetFieldID(pi_cls, "signatures", "[Landroid/content/pm/Signature;");
    env->DeleteLocalRef(pi_cls);
    jobject sig_obj = env->GetObjectField(pi_obj, signatures_fid);
    env->DeleteLocalRef(pi_obj);
    jobjectArray sigs = reinterpret_cast<jobjectArray>(sig_obj);

    jclass signature_cls = env->FindClass("android/content/pm/Signature");
    jmethodID to_byte_array_mid = env->GetMethodID(signature_cls, "toByteArray",
                                                   "()[B");
    jbyteArray sig_bytes = reinterpret_cast<jbyteArray>(env->CallObjectMethod(
            env->GetObjectArrayElement(sigs, 0), to_byte_array_mid));
    jclass certificate_factory_cls = env->FindClass(
            "java/security/cert/CertificateFactory");
    jmethodID get_certificate_instance_mid = env->GetStaticMethodID(
            certificate_factory_cls, "getInstance", "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
    jobject certificate_factory_obj = env->CallStaticObjectMethod(
            certificate_factory_cls, get_certificate_instance_mid,
            env->NewStringUTF("X509"));
    jmethodID generate_certificate_mid = env->GetMethodID(certificate_factory_cls, "generateCertificate",
            "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;");
    env->DeleteLocalRef(certificate_factory_cls);
    jclass certificate_cls = env->FindClass("java/security/cert/Certificate");
    jclass byte_input_stream_cls = env->FindClass(
            "java/io/ByteArrayInputStream");
    jmethodID new_sig_bytes_is_mid = env->GetMethodID(byte_input_stream_cls, "<init>", "([B)V");
    jobject sig_bytes_is = env->NewObject(byte_input_stream_cls,
                             new_sig_bytes_is_mid, sig_bytes);
    env->DeleteLocalRef(sig_bytes);
    env->DeleteLocalRef(byte_input_stream_cls);
    jobject cert = env->CallObjectMethod(certificate_factory_obj, generate_certificate_mid, sig_bytes_is);
    env->DeleteLocalRef(sig_bytes_is);
    env->DeleteLocalRef(certificate_factory_obj);
    jmethodID get_pubic_key_mid = env->GetMethodID(certificate_cls, "getPublicKey", "()Ljava/security/PublicKey;");
    env->DeleteLocalRef(certificate_cls);

    jobject publicKey = env->CallObjectMethod(cert, get_pubic_key_mid);
    jclass publicKey_cls = env->GetObjectClass(publicKey);
    jmethodID toString_mid = env->GetMethodID(publicKey_cls, "toString", "()Ljava/lang/String;");
    jstring publicKey_str = static_cast<jstring>(env->CallObjectMethod(publicKey, toString_mid));
    env->DeleteLocalRef(cert);
    env->DeleteLocalRef(publicKey_cls);
    env->DeleteLocalRef(publicKey);

    jclass string_cls = env->GetObjectClass(publicKey_str);
    jmethodID indexOf_mid = env->GetMethodID(string_cls, "indexOf", "(Ljava/lang/String;)I");
    jstring param = env->NewStringUTF("modulus");
    jint aa = env->CallIntMethod(publicKey_str, indexOf_mid, param);
    jstring param2 = env->NewStringUTF("publicExponent");
    jint bb = env->CallIntMethod(publicKey_str, indexOf_mid, param2);
    jmethodID substring_mid = env->GetMethodID(string_cls, "substring", "(II)Ljava/lang/String;");
    jstring publicKey2_str = static_cast<jstring>(env->CallObjectMethod(publicKey_str,
                               substring_mid, aa + 8, bb - 1));
    return publicKey2_str;
}

//Get apk signature
jstring getPackageSignature(JNIEnv *env, jobject thizz, jobject context) {
    jclass native_clazz = env->GetObjectClass(context);
    // getPackageManager method ID
    jmethodID methodID_func = env->GetMethodID(native_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    // get PackageManager
    jobject package_manager = env->CallObjectMethod(context, methodID_func);
    // get PackageManager class
    jclass pm_clazz = env->GetObjectClass(package_manager);
    // get getPackageInfo method ID
    jmethodID methodID_pm = env->GetMethodID(pm_clazz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    //get package name
    jmethodID methodID_packagename = env->GetMethodID(native_clazz, "getPackageName", "()Ljava/lang/String;");
    jstring name_str = static_cast<jstring>(env->CallObjectMethod(context, methodID_packagename));

    jobject package_info = env->CallObjectMethod(package_manager, methodID_pm, name_str, 64);
    // get PackageInfo class
    jclass pi_clazz = env->GetObjectClass(package_info);
    // get signature field ID
    jfieldID fieldID_signatures = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
    // get sign arr PackageInfo.signatures Signature[]
    jobject signatures = env->GetObjectField(package_info, fieldID_signatures);

    jobjectArray signature_jarr = reinterpret_cast<jobjectArray>(signatures);
    // get signature
    jobject signature = env->GetObjectArrayElement(signature_jarr, 0);

    // get Signature class
    jclass s_clazz = env->GetObjectClass(signature);

    // get hashCode
    jmethodID methodID_hc = env->GetMethodID(s_clazz, "hashCode", "()I");
    int hash_code = env->CallIntMethod(signature, methodID_hc);
    char str[100];
    sprintf(str, "%u", hash_code);
    jstring sign = env->NewStringUTF(str);
    return sign;
}

jstring getPackageName(JNIEnv *env, jobject thizz, jobject thiz) {
    jclass native_clazz = env->GetObjectClass(thiz);
    // get PackageManager method ID
    jmethodID methodID_func = env->GetMethodID(native_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    // get PackageManager
    jobject package_manager = env->CallObjectMethod(thiz, methodID_func);
    // get PackageManager class
    jclass pm_clazz = env->GetObjectClass(package_manager);
    // get getPackageInfo method ID
    jmethodID methodID_pm = env->GetMethodID(pm_clazz, "getPackageInfo",
                              "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    //get package name
    jmethodID methodID_packagename = env->GetMethodID(native_clazz, "getPackageName", "()Ljava/lang/String;");
    jstring name_str = static_cast<jstring>(env->CallObjectMethod(thiz, methodID_packagename));
    return name_str;
}

//ysdq and lightning signature hashcode
jstring getYsdqSignature(JNIEnv *env, jclass thizz) {
    char key[] = "24305";
    jstring aesStr = getAppendStr(env, thizz, (env)->NewStringUTF(MOUNTAIN), (env)->NewStringUTF(key));
    return aesStr;
}

//cjsp signature hashcode
jstring getCjspSignature(JNIEnv *env, jclass thizz) {
    char key[] = "44025";
    jstring desStr = getAppendStr(env, thizz, (env)->NewStringUTF(RIVER), (env)->NewStringUTF(key));
    return desStr;

}

//kuaikan signature hashcode
jstring getKuaikanSignature(JNIEnv *env, jclass thizz) {
    char key[] = "04351";
    jstring rsaStr = getAppendStr(env, thizz, (env)->NewStringUTF(CLOUD), (env)->NewStringUTF(key));
    return rsaStr;
    //LOG("pre sign str: %s", (env)->GetStringUTFChars(aesStr, NULL));
}

//signature match
jstring isValidate(JNIEnv *env, jclass thizz, jobject context) {
    jstring sigStr = getPackageSignature(env, thizz, context);
    const char *sigChars = (env)->GetStringUTFChars(sigStr, NULL);
    //LOG("jni app sign : %s", appSignChars);
    jstring aesStr = getYsdqSignature(env, thizz);
    jstring desStr = getCjspSignature(env, thizz);
    jstring rsaStr = getKuaikanSignature(env, thizz);
    const char *aesChars = (env)->GetStringUTFChars(aesStr, NULL);
    const char *desChars = (env)->GetStringUTFChars(desStr, NULL);
    const char *rsaChars = (env)->GetStringUTFChars(rsaStr, NULL);
    if (strcmp(sigChars, aesChars) == 0 || strcmp(sigChars, desChars) == 0
        ||  strcmp(sigChars, rsaChars) == 0) {
        return sigStr;
    } else {
        return NULL;
    }
}

//Get timestamp
jstring getSystemTime(JNIEnv *env) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    int64 t = tv.tv_sec;
    t *= 1000;
    t += tv.tv_usec / 1000;
    char str[100];
    sprintf(str, "%lld", t);
    jstring result = env->NewStringUTF(str);
    return result;
}

__attribute__((section (".mytext"))) JNICALL jstring getToken(JNIEnv *env, jclass obj, jobject context, jstring timestamp) {
    //Signature
    jstring result = isValidate(env, obj, context);

    //const char *resultChars = (env)->GetStringUTFChars(result, NULL);
    //LOG("jni signature : %s", resultChars);

    if (result != NULL) {
        //Secret code
        char key[] = "PyuIK";
        jstring toSign = getAppendStr(env, obj, (env)->NewStringUTF(AES), (env)->NewStringUTF(key));
        toSign = getAppendStr(env, obj, toSign, (env)->NewStringUTF(DES));

         //const char *toSignChars = (env)->GetStringUTFChars(toSign, NULL);
         //LOG("jni secret code : %s", toSignChars);

        //append
        toSign = getAppendStr(env, obj, result, toSign);
        toSign = getAppendStr(env, obj, toSign, timestamp);
        result = getSecStr(env, toSign);
        return result;
    }
    return (env)->NewStringUTF("");
}

__attribute__((section (".mytext"))) JNICALL jstring getCode(JNIEnv *env, jclass obj, jobject context) {
    jstring result = isValidate(env, obj, context);
    if (result != NULL) {
        //Secret code
        char key[] = "PyuIK";
        jstring toSign = getAppendStr(env, obj, (env)->NewStringUTF(AES), (env)->NewStringUTF(key));
        toSign = getAppendStr(env, obj, toSign, (env)->NewStringUTF(DES));
        return toSign;
    }
    return (env)->NewStringUTF("");
}